﻿
using System;
using System.Collections.Generic;
using My.Ioc;

namespace TreeBuilder
{
    #region Tree/Node Types

    public interface INode
    {
        string ParentName { get; set; }
        string Name { get; }
        IEnumerable<INode> ChildNodes { get; }
    }

    public abstract class Node : INode
    {
        string _name;

        public string ParentName { get; set; }
        public string Name
        {
            get
            {
                _name = _name ?? GetType().Name;
                return _name;
            }
        }

        public virtual IEnumerable<INode> ChildNodes
        {
            get { return null; }
        }
    }

    #region Level 1

    public class Tree
    {
        readonly string _name;
        readonly IEnumerable<INode> _childNodes;

        public Tree(IEnumerable<INode> childNodes)
        {
            if (childNodes == null)
                throw new ArgumentException();
            _name = GetType().Name;
            foreach (var childNode in childNodes)
                childNode.ParentName = _name;
            _childNodes = childNodes;
        }

        public string Name
        {
            get { return _name; }
        }

        public IEnumerable<INode> ChildNodes
        {
            get { return _childNodes; }
        }
    }

    #endregion

    #region Level 2

    public abstract class CompositeNode : Node
    {
        readonly IEnumerable<INode> _childNodes;

        protected CompositeNode(IEnumerable<INode> childNodes)
        {
            if (childNodes == null)
                throw new ArgumentException();
            foreach (var childNode in childNodes)
                childNode.ParentName = Name;
            _childNodes = childNodes;
        }

        public override IEnumerable<INode> ChildNodes
        {
            get { return _childNodes; }
        }
    }

    public class ListNode : CompositeNode
    {
        public ListNode(List<INode> childNodes)
            : base(childNodes)
        {
        }
    }

    public class ArrayNode : CompositeNode
    {
        public ArrayNode(INode[] childNodes)
            : base(childNodes)
        {
        }
    }

    #endregion

    #region Level 3

    public class ChildNode1 : Node
    {
    }

    public class ChildNode2 : Node
    {
    }

    public class ChildNode3 : Node
    {
    }

    public class ChildNode4 : Node
    {
    }

    public class ChildNode5 : Node
    {
    }

    public class ChildNode6 : Node
    {
    }

    public class ChildNode7 : Node
    {
    }

    public class ChildNode8 : Node
    {
    }

    #endregion

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            IObjectContainer container = new ObjectContainer(false);
            Register(container);

            // Try to get an observer
            IObjectObserver<Tree> treeObserver;
            if (!container.TryGetObserver(out treeObserver))
                throw new InvalidOperationException();

            // Resolve the tree using the observer
            var tree = container.Resolve(treeObserver);
            //var tree = container.Resolve<Tree>();
            PrintTreeMembers(tree, 1);

            // Add more nodes at runtime
            container.Register(typeof(INode), typeof(ChildNode8))
                .WhenParentMetadata((mata) => string.Equals("ArrayNode", mata));
            container.Register<INode, ChildNode7>()
                .WhenParentTypeIs<ListNode>();

            // Commit the registrations to the registry as usual.
            container.CommitRegistrations();

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();

            // Resolve the tree again
            tree = container.Resolve(treeObserver);
            //tree = container.Resolve<Tree>();
            PrintTreeMembers(tree, 2);

            Console.ReadLine();
        }

        static void Register(IObjectContainer container)
        {
            container.Register<Tree>();

            container.Register<INode, ArrayNode>()
                .WhenParentTypeIs<Tree>()
                .Set("ArrayNode");
            container.Register<INode, ListNode>()
                .WhenParentTypeIs<Tree>()
                .Set("ListNode");

            #region Inject into ArrayNode

            container.Register(typeof(INode), typeof(ChildNode1))
                .WhenParentMetadata((mata) => string.Equals("ArrayNode", mata));

            container.Register<INode, ChildNode2>()
                .WhenParentTypeIs<ArrayNode>();

            container.Register<INode, ChildNode3>()
                .WhenParentTypeIs<ArrayNode>();

            container.Register<INode, ChildNode4>()
                .WhenParentTypeIs<ArrayNode>();

            #endregion

            #region Inject into ListNode

            container.Register<INode, ChildNode5>()
                .WhenParentTypeIs<ListNode>();

            container.Register<INode, ChildNode6>()
                .WhenParentTypeIs<ListNode>();

            #endregion

            // Commit the registrations to the registry.
            container.CommitRegistrations();
        }

        static void PrintTreeMembers(Tree tree, int time)
        {
            if (tree == null)
                throw new ArgumentException();

            Console.WriteLine(time);
            Console.WriteLine("=================================================");

            Console.WriteLine(tree.Name);

            // Breadth first traversal
            var allNodes = new List<INode>();
            if (tree.ChildNodes == null)
                return;
            allNodes.AddRange(tree.ChildNodes);
            for (int i = 0; i < allNodes.Count; i++)
            {
                var node = allNodes[i];
                Console.WriteLine(node.ParentName + "/" + node.Name);
                if (node.ChildNodes != null)
                    allNodes.AddRange(node.ChildNodes);
            }
        }
    }
}
